import { Http } from "./http";
import type { FieldRule, FieldTextAlign } from "vant";
import type { interForm } from './type'
import Schema, { type InternalRuleItem, type RuleItem, type ValidateFieldsError } from 'async-validator';
import { reactive, toValue, type VNode } from "vue";


const isPhone = window.isPhone
export class Form<T extends Record<string, any> & { id?: number | string }> extends Http {
    id?: number | string = undefined;
    data = reactive({} as T)
    select?: Record<keyof T, object>;
    label_width: string | number = '4em';
    labels = reactive({} as { [key in keyof T & string]?: interForm<T, key> });
    url: { info: string; add: string; update: string; } = {
        info: '',
        add: '',
        update: ''
    };
    rule?: { [key in keyof T & string]?: RuleItem[] };
    spance: number = 20;
    inputAlign: FieldTextAlign = 'right';
    border: boolean = true;
    upload: string | (keyof T)[] = []
    declare json: (keyof T & string)[];
    editButton?: (isPhone: boolean) => VNode[]

    set _show(value: boolean) {
        if (!value) {
            this.title = ''
            this.id = undefined
        } else {
            this.init()
            this.#setData({} as T)
        }
        this.show = value
    }
    get _show() {
        return this.show
    }
    #setData(data: T) {
        this.data = reactive(this.utils_readWrite(data))
    }
    init() { }
    getUrl(url: 'info' | 'add' | 'update') {
        return this.url[url] || this.baseUrl + '/' + url
    }
    get infoUrl() {
        return this.getUrl('info')
    }
    get addUrl() {
        return this.getUrl('add')
    }
    get updateUrl() {
        return this.getUrl('update')
    }
    static getVantRule(rule: RuleItem[]): FieldRule[] {
        return rule.map(item => {
            const { required, message, pattern, validator } = item
            return {
                required, message, pattern, validateEmpty: false,
                validator: validator ? (value: string | number | boolean, rule: InternalRuleItem) => {
                    return validator(rule, value, () => true, {}, {})
                } : () => true
            } as FieldRule
        })
    }
    afterSetData(res: any) { return res }
    afterUpdateData(res: any) { res }
    defaultInputChange<U extends keyof T & string>(data: T, prop: U, value: T[U]) {
        data[prop] = value
    }
    async setData(unShowToast?: boolean): Promise<T | false> {
        if (!this.id) {
            throw new Error("获取详情时id不存在");
        }
        const res = await this.request(this.infoUrl, { method: 'GET', parames: { id: this.id }, unShowToast })
        if (this.json) {
            this.json.forEach(item => {
                return this.#handleJson(res, item);
            })
        }
        if (this.afterSetData) this.#setData(this.afterSetData(res))
        else this.#setData(res || {} as T)
        return res || false;
    }

    async updateData(unToast = false): Promise<T | false> {
        //结构取消联动，防止json类型改变时报错
        const data = { ...toValue(this.data) } as T
        const err = await this.#validater(data)
        if (err) return false
        if (Array.isArray(this.upload)) {
            const promise = this.upload.map(item => {
                return this.#handleUpload(data, item);
            })
            const res = await Promise.all(promise)
            if (res.includes(false)) return false;
        }
        if (this.json) {
            this.json.forEach(item => {
                return this.#handleToJson(data, item);
            })
        }
        const res = this.id ? await this.#updateExistingData(data, unToast) : await this.#addNewData(data, unToast);
        if (res) {
            this.show = false;
            if (this.afterUpdateData) this.afterUpdateData(res)
            return res;
        }
        return false
    }

    async #updateExistingData(data: T, unToast?: boolean): Promise<T | false> {
        return await this.request(this.updateUrl, { method: 'POST', unShowToast: unToast, successToast: true, parames: { id: this.id }, data: { ...data } });
    }

    async #addNewData(data: T, unToast?: boolean): Promise<T | false> {
        return await this.request(this.addUrl, { method: 'POST', unShowToast: unToast, successToast: true, data });
    }

    async #handleUpload(data: T, upload: keyof T): Promise<boolean> {
        const url = data[upload]
        if (url) {
            if (Array.isArray(url)) {
                const res = await Promise.all(url.map((item: string) => this.#uploadItem(item)));
                if (res.includes('error')) return false;
                (data[upload] as string) = JSON.stringify(res);
            } else {
                const res = await this.#uploadItem(url as string);
                if (res === 'error') return false;
                (data[upload] as string) = res;
            }
        }
        return true;
    }
    async #handleToJson(data: T, json: keyof T): Promise<boolean> {
        const item = data[json]
        data[json] = JSON.stringify(item) as any
        return true
    }

    async #handleJson(data: T, json: keyof T): Promise<boolean> {
        const item = data[json]
        data[json] = JSON.parse(item) as any
        return true
    }

    async #uploadItem(url: string): Promise<string> {
        if (typeof url !== 'string') return '';
        if (/^blob:/.test(url)) {
            return this.imgUpload(url);
        }
        return url
    }
    async #validater(data: T): Promise<boolean> {
        if (!this.rule) return false;
        try {
            const validator = new Schema(this.rule as Record<string, RuleItem[]>);
            await validator.validate(data, { first: true });
            return false;
        } catch (error: any) {
            this.#handleValidationError(error);
            return true;
        }
    }

    #handleValidationError(error: ValidateFieldsError) {
        if (Object.hasOwn(error, 'errors')) {
            const { message, field } = error.errors[0];
            if (isPhone) {
                this.#scrollToErrorField(field);
            }
            this.showToast(message, 'error');
        } else {
            console.log(error);
            throw error;
        }
    }

    #scrollToErrorField(field?: string) {
        const errDom = field ? document.getElementById(field) : null;
        if (!errDom) return
        errDom.scrollIntoView({ block: 'end' });
        errDom.setAttribute('style', 'border-bottom:1px solid rgb(238, 10, 36)');
        setTimeout(() => {
            errDom?.setAttribute('style', 'border:none');
        }, 1000);
    }
}
